package com.huntero.picscale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

import java.util.Arrays;

/**
 * @ClassName: MatrixImageView
 * @Description:  带放大、缩小、移动效果的ImageView
 *
 */
@SuppressLint("AppCompatCustomView")
public class ScaleImageView extends ImageView{

    public static final String TAG = "ScaleImageView";
    private float mLastX, mLastY;
    private float mCurrentX, mCurrentY;
    private PointF mScaleCenter = new PointF();

    public static final int MODE_NONE = 0;
    public static final int MODE_DRAG = 1;
    public static final int MODE_SCALE = 2;
    private int mMode = MODE_NONE;

    private final float[] mOriginalValues = new float[9];
    private final float[] curValues = new float[9];
    Matrix mOriginalMatrix;
    Matrix mCurrentMatrix = new Matrix();
    private float mImageHeight;
    private float mImageWidth;
    private float mStartDist;
    private float mWidgetWidth;
    private float mWidgetHeight;

    OnChildMovingListener mOnChildMovingListener = null;

    public void setmOnChildMovingListener(OnChildMovingListener onChildMovingListener) {
        this.mOnChildMovingListener = onChildMovingListener;
    }

    boolean dragging = false;
    private void startDragging(){
        dragging = true;
        if (this.mOnChildMovingListener != null) {
            this.mOnChildMovingListener.onStartMoving();
        }
    }
    private void stopDragging(){
        dragging = false;
        if (this.mOnChildMovingListener != null) {
            this.mOnChildMovingListener.onStopMoving();
        }
    }
    private boolean isDragging(){
        return dragging;
    }

    public ScaleImageView(Context context) {
        super(context);
        init();
    }

    public ScaleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ScaleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                Log.i(TAG, "onPreDraw: ");
                getViewTreeObserver().removeOnPreDrawListener(this);
                obtainImageInfo();
                setScaleType(ImageView.ScaleType.MATRIX);
                return true;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                //单指按下
                Log.i(TAG, "onTouchEvent: 单指按下，image doDrag");
                startDragging();
                mLastX = event.getX();
                mLastY = event.getY();
                mMode = MODE_DRAG;
                Log.i(TAG, "onTouch: start=" + mLastX + ", " + mLastY);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                //多指按下
                mMode = MODE_SCALE;
                mStartDist = calcuDistance(event);
                break;
            case MotionEvent.ACTION_MOVE:
                //移动
                mCurrentX = event.getX();
                mCurrentY = event.getY();
                Log.i(TAG, "onTouch: curr=" + mCurrentX + ", " + mCurrentY);

                if (mMode == MODE_DRAG) {//移动
                    doDrag();
                } else if (mMode == MODE_SCALE) {//缩放
                    doScale(event);
                }
                mLastX = mCurrentX;
                mLastY = mCurrentY;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //多指抬起：修改平移和缩放的标识的时机
                Log.i(TAG, "onTouch: ACTION_POINTER_UP");
                mMode = MODE_DRAG;
                //
                resetScaleOrBounds();
                break;
            case MotionEvent.ACTION_UP:
                //单指抬起
                Log.i(TAG, "onTouch: ACTION_UP");
                calcuBounds();
                mMode = MODE_NONE;
                break;
        }
        Log.i(TAG, "onTouch: action=" + (action & MotionEvent.ACTION_MASK) + ", mode=" + mMode);
        return true;
    }

    private boolean isScaled(){
        getImageMatrix().getValues(curValues);
        float scale = curValues[Matrix.MSCALE_X];
        return scale != mOriginalValues[Matrix.MSCALE_X];
    }
    private float calcuDistance(MotionEvent event){
        float x2 = event.getX(1);
        float y2 = event.getY(1);
        float x1 = event.getX(0);
        float y1 = event.getY(0);
        float dx = x2 - x1;
        float dy = y2 - y1;
        mScaleCenter.set((x1 + x2) / 2,(y1 + y2) / 2);
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    //缩放
    private void doScale(MotionEvent event) {
        mCurrentMatrix.set(getImageMatrix());
        float endDist = calcuDistance(event);
        float scale = endDist / mStartDist;

        Log.i(TAG, "onTouch: scale=" + scale);
        mCurrentMatrix.postScale(scale, scale, mScaleCenter.x, mScaleCenter.y);
        setImageMatrix(mCurrentMatrix);
        mStartDist = endDist;
    }

    //滑动
    private void doDrag() {
        if (isDragging() && isScaled()) {
            //缩放，可以移动
            mCurrentMatrix.set(getImageMatrix());
            mCurrentMatrix.getValues(curValues);

            float dx = mCurrentX - mLastX;
            float dy = mCurrentY - mLastY;

            double dist = Math.sqrt(dx * dx + dy * dy);
            if (dist >= 2f) {
                int x = isOutOfBound(mWidgetWidth, mImageWidth * curValues[Matrix.MSCALE_X], curValues[Matrix.MTRANS_X] + dx);
                int y = isOutOfBound(mWidgetHeight, mImageHeight * curValues[Matrix.MSCALE_Y], curValues[Matrix.MTRANS_Y] + dy);
                Log.i(TAG, "doDrag: x=" + x + ", dx=" + dx + ", y=" + y + ", dy=" + dy);

                if ((x == P_OUTOF_BOUND && y == P_OUTOF_BOUND) ||
                        (x == P_IN_AXIS && y == P_OUTOF_BOUND && Math.abs(dy) >= 10) ||
                        (y == P_IN_AXIS && x == P_OUTOF_BOUND && Math.abs(dx) >= 10) ||
                        (x == P_OUTOF_BOUND && y == P_IN_CENTER && Math.abs(dx) >= 10)) {
                    Log.i(TAG, "doDrag: >>stop dragging");
                    stopDragging();
                    return;
                }
                if (x != P_IN_CENTER) {
                    dx = 0f;
                }
                if (y != P_IN_CENTER) {
                    dy = 0f;
                }

                mCurrentMatrix.postTranslate(dx, dy);
                setImageMatrix(mCurrentMatrix);
            }
        }else{
            Log.i(TAG, "doDrag: no scale but no dragging");
            stopDragging();
        }
    }

    private void resetScaleOrBounds() {
        calcuScales();
        //是否需要移动
        calcuBounds();
    }

    private void calcuScales() {
        mCurrentMatrix.set(getImageMatrix());
        mCurrentMatrix.getValues(curValues);
        Log.i(TAG, "calcuScales: orig=" + Arrays.toString(mOriginalValues));
        Log.i(TAG, "calcuScales: curr=" + Arrays.toString(curValues));

        //判断是否超过最大缩放值
        float scale = 1f;
        float p = curValues[Matrix.MSCALE_X] / mOriginalValues[Matrix.MSCALE_X];
        if (p  >= 8f) {
            scale = 8f / p;
        }

        //判断是否低于原始显示的缩放值
        if (p <= 1f) {
            scale = 1 / p;
        }

        Log.i(TAG, "calcuScales: ratio=" + p + ", scale=" + scale);
        if(scale != 1f) {
            mCurrentMatrix.postScale(scale, scale, mScaleCenter.x, mScaleCenter.y);
            setImageMatrix(mCurrentMatrix);
        }
    }

    private void calcuBounds() {
        mCurrentMatrix.set(getImageMatrix());
        mCurrentMatrix.getValues(curValues);
        Log.i(TAG, "calcuBounds: curr=" + Arrays.toString(curValues));

        float dx = calcuDxBound(curValues);
        float dy = calcuDyBound(curValues);

        if(dx != 0 || dy != 0) {
            mCurrentMatrix.postTranslate(dx, dy);
            setImageMatrix(mCurrentMatrix);
        }
    }

    private float calcuDyBound(float[] currValues) {
        float dy = 0f;
        float displayHeight = mImageHeight * currValues[Matrix.MSCALE_Y];
        int st = isOutOfBound(mWidgetHeight, displayHeight, currValues[Matrix.MTRANS_Y]);
        switch (st){
            case 1:
                dy = (mWidgetHeight - displayHeight) / 2 - currValues[Matrix.MTRANS_Y];
                break;
            case -1:
                //防止超区域
                float max = 0,min = mWidgetHeight - displayHeight;
                float y = currValues[Matrix.MTRANS_Y];

                if(y < min) {
                    dy = min - y;

                }
                if(y > max){
                    dy = max - y;
                }
                break;
        }
//        if(mWidgetHeight >= displayHeight){
//            //居中y轴，x轴不动
//            dy = (mWidgetHeight - displayHeight) / 2 - currValues[Matrix.MTRANS_Y];
//        }else{
//            //防止超区域
//            float max = 0,min = mWidgetHeight - displayHeight;
//            float y = currValues[Matrix.MTRANS_Y];
//
//            if(y < min) {
//                dy = min - y;
//
//            }
//            if(y > max){
//                dy = max - y;
//            }
//        }
        Log.i(TAG, "calcuDyBound: widgetH=" + mWidgetHeight + ", display=" + displayHeight + ", y=" + currValues[Matrix.MTRANS_Y] + ", dy=" + dy);
        return dy;
    }

    private float calcuDxBound(float[] currValues) {
        float dx = 0f;
        float displayWidth = mImageWidth * currValues[Matrix.MSCALE_X];
        int st = isOutOfBound(mWidgetWidth, displayWidth, currValues[Matrix.MTRANS_X]);
        switch (st){
            case 1:
                dx = (mWidgetWidth - displayWidth) / 2 - currValues[Matrix.MTRANS_X];
                break;
            case -1:
                float max = getX(),min = mWidgetWidth - displayWidth;
                Log.i(TAG, "calcuDxBound: max=" + max);
                float x = currValues[Matrix.MTRANS_X];
                if(x < min){
                    dx = min - x;
                }
                if(x > max){
                    dx = max - x;
                }
                break;
        }
//        if(mWidgetWidth >= displayWidth){
//            //居中x轴，y轴不动
//            dx = (mWidgetWidth - displayWidth) / 2 - currValues[Matrix.MTRANS_X];
//        }else{
//            //防止超区域
//            float max = 0,min = mWidgetWidth - displayWidth;
//            float x = currValues[Matrix.MTRANS_X];
//            if(x < min){
//                dx = min - x;
//            }
//            if(x > max){
//                dx = max - x;
//            }
//        }
        Log.i(TAG, "calcuDxBound: widgetW=" + mWidgetWidth + ", display=" + displayWidth + ", x=" +  currValues[Matrix.MTRANS_X] + ", dx=" + dx);
        return dx;
    }

    public static final int P_IN_AXIS = 1;
    public static final int P_IN_CENTER = 0;
    public static final int P_OUTOF_BOUND = -1;
    /**
     *
     * @param widget
     * @param display
     * @param current
     * @return 1：居中显示 0：未超边界 -1：超出边界
     */
    private int isOutOfBound(float widget, float display, float current){
        if(widget >= display){
            //内部居中显示
            return 1;
        }else{
            float max=0,min=widget-display;
            if(current < min || current > max){
                return -1;
            }
            return 0;
        }
    }

    private void obtainImageInfo() {
        mWidgetWidth = getWidth();
        mWidgetHeight = getHeight();
        mOriginalMatrix = getImageMatrix();
        mOriginalMatrix.getValues(mOriginalValues);

        Log.i(TAG, "obtainImageInfo: mOriginalValues=" + Arrays.toString(mOriginalValues));
        mImageWidth = (mWidgetWidth - mOriginalValues[Matrix.MTRANS_X] * 2) / mOriginalValues[Matrix.MSCALE_X];
        mImageHeight = (mWidgetHeight - mOriginalValues[Matrix.MTRANS_Y] * 2) / mOriginalValues[Matrix.MSCALE_Y];
        Log.i(TAG, "obtainImageInfo: w=" + mImageWidth + ", h=" + mImageHeight);
    }

}